﻿using System;
using System.Collections;
using System.Collections.Generic;
using FunctionalCLR.Core;
using Microsoft.SolverFoundation.Services;
using FunctionalCLR.Core.Function.Arithmetics;
using FunctionalCLR.Shell.Modeling;

namespace FunctionalCLR.Shell.Optimization {

    /// <summary>
    /// The form is 
    /// a = minimize(1-2*x^2, x, 0, 100);
    /// echo(a); # the result is 
    /// echo(x); # the output is
    /// </summary>
    public abstract class OptimizeNode : ContextFuncNode {

        private bool isMaximizing = false;

        public OptimizeNode(bool isMaximizing)
            : base() {
            this.isMaximizing = isMaximizing;
        }

        public override object Evaluate() {
            int count = Parameters.Count;
            if (count < 2) {
                throw new Exception(
                    "Usage: minimize(expr, {parameters}, [{constraints}]);");
            }

            double result = 0;
            Node node = null;
            if (Parameters[count - 1] is FormulateNode)
            {
                node = Parameters[count - 1].Evaluate() as Node;
            }
            else
            {
                node = Parameters[count - 1];
            }

            if (!(Parameters[count - 2] is ListNode)) {
                throw new ArgumentException(
                    "Parameters should be a List");
            }

            ListNode ln = Parameters[count - 2] as ListNode;
            if (ln.Parameters.Count < 1) {
                throw new ArgumentException(
                    "Please specify a least one parameter.");
            }

            if (ln.Parameters.Count > 1) {
                // use Solver Foundation to execute the job
                VariableMapper mapper = new VariableMapper();
                string value = string.Empty;

                List<string> p = new List<string>();
                ListNode paramters = Parameters[count - 2] as ListNode;
                foreach (Node n in paramters.Parameters) {
                    string parameter
                        = DefaultDependencyManager.Instance.ExpressionCompiler.Compile(n);
                    value = mapper.Add(parameter);
                    p.Add(value);
                }

                List<string> c = new List<string>();
                ListNode constraints 
                    = Parameters[count - 3] as ListNode;
                foreach (Node n in constraints.Parameters) {
                    string constraint
                        = DefaultDependencyManager.Instance.ExpressionCompiler.Compile(n);
                    value = Utils.ReplaceString(constraint, mapper);
                    c.Add(value);
                }

                string expr = DefaultDependencyManager.Instance.ExpressionCompiler.Compile(node);
                value = Utils.ReplaceString(expr, mapper);

                OMLSolver solver = new OMLSolver();
                Solution solution = solver.Solve(
                    value, isMaximizing, p.ToArray(), c.ToArray());

                foreach (Decision descision in solution.Decisions) {
                    this.Context.Assign(
                        descision.Name, descision.ToDouble());
                }

                IEnumerator<Goal> goals = solution.Goals.GetEnumerator();
                goals.MoveNext();

                return goals.Current.ToDouble();
            }

            // using brent method
            string variableName = ln.Parameters[0].Value;

            if (isMaximizing)
            {
                OperatorNode root = new SubNode();
                root.Left = new NumberNode(0);
                root.Right = node;

                node = root;
            }

            IUnaryParameterFunction func
                = Context.UnaryFunctionFactory.Create(
                variableName, node);

            double from;
            double to;
            double min = 0;
            if (Parameters.Count > 2) {
                from = Convert.ToDouble(Parameters[count - 3].Evaluate());
                to = Convert.ToDouble(Parameters[count - 4].Evaluate());
            } else {
                from = Double.MinValue;
                to = Double.MaxValue;
            }

            result = BrentMinimizationAlgorithm.Minimize(
                func, from, to, 1e-6, ref min);

            this.Context.Assign(variableName, min);

            if (isMaximizing) {
                result = -result;
            }

            return result;
        }
    }
}
